#include <execinfo.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>


typedef void (*Callback)(void *ptr,char *info);

typedef struct watch_node{
	void *real_ptr;
	int real_size; 
	void **target_ptr2;
	void *target_ptr;
	int target_size;
	Callback cb;
	char *backtrace_info_for_cb;
	int backtrace_info_size_allowed;
	struct watch_node *next;
}Node;

Node *head = NULL;
int inited = 0;

void munmap_real_ptr(void *ptr,int size){
	static void *pending_munmap_ptr = NULL;
	static int pending_munmap_size = 0;
	if(pending_munmap_ptr != NULL && pending_munmap_size != 0){
		if (munmap(pending_munmap_ptr,pending_munmap_size) == -1) {
			perror("munmap");
		}
	}
	pending_munmap_ptr = ptr;
	pending_munmap_size = size;
	
}

Node *get_last_node(){
	Node *last_node = head;
	if(last_node == NULL) return last_node;
	while(last_node->next != NULL) last_node = last_node->next;
	return last_node;
}

Node *alloc_new_node(void **target, int size, Callback cb, char *info,int info_size){
	Node *node = malloc(sizeof(Node));
	Node *last_node = get_last_node();
	if(last_node == NULL) head = node;
	else last_node->next = node;
	memset(node,0,sizeof(Node));
	node->next = NULL;
	node->cb = cb;
	node->target_ptr2 = target;
	node->target_ptr = *target;
	node->target_size = size;
	node->backtrace_info_for_cb = info;
	node->backtrace_info_size_allowed = info_size;

	int page_size = getpagesize();
	int real_size = (size/page_size + ((size%page_size > 0)?1:0))*page_size;
	/* Should the recovery to READ|WRITE access not be achieved, continuous writing attempts will ensue, 
	 * causing repeated SIGSEGV signals until the operation succeeds
	*/
	void* ptr = mmap(NULL,real_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (ptr == MAP_FAILED) {
        perror("mmap");
        free(node);
		return NULL;
    }

    void* aligned_ptr = (void*)(((uintptr_t)ptr + size - 1) & ~(size - 1));
	memcpy(aligned_ptr, *target, size);
	*target = aligned_ptr;
	node->real_ptr = aligned_ptr;
	node->real_size = real_size;
	printf("%s node=%p targetptr2=%p targetptr=%p size=%d real_ptr=%p real_size=%d page_size=%d\n",
			__func__,node,node->target_ptr2,node->target_ptr,node->target_size,
			node->real_ptr,node->real_size,page_size);
	return node;
}

int free_node(void *real_ptr){
	Node *node = head;
	Node *pre_node = node;
	if(node == NULL) return -1;
	while(node != NULL && node->real_ptr != real_ptr) {
		pre_node = node;
		node = node->next;
	}
	if(node==NULL) {
		printf("Can not find ptr %p",real_ptr);
		return -1;
	}else{
		*(node->target_ptr2) = node->target_ptr;
		memcpy(node->target_ptr, node->real_ptr, node->target_size);
		/*
		 * Avoid munmap() here, as writing to the variable (e.g., *i = 1) will result in a SIGSEGV.
		 * The reason is that the pointer value for 'i' has already been loaded into a register,
		 * but if we call munmap() here, the memory becomes unmapped and inaccessible.
		 * 
		 * When attempting to write in a different context, such as from a debugger (GDB),
		 * where the pointer value is not loaded into a register, the write operation succeeds.
		 * 
		 * Therefore, refrain from munmap() before ensuring that the write operation has been completed successfully.
		 */
		munmap_real_ptr(node->real_ptr,node->real_size);
		pre_node->next=node->next;
		if(node == head) head = head->next;
		free(node);
	}
	return 0;

}
void get_backtrace_info(char *info, int info_size) {
	int backtrace_depth = 64;
    void *array[backtrace_depth];
    size_t size;
    char **strings;
    size_t offset = 0; 
    size_t space_left = info_size - offset;
    if (info == NULL || info_size <= 0) {
        snprintf(info + offset, space_left, "info or info_size unvailable\n");
        return;
    }
    size = backtrace(array, backtrace_depth);
    strings = backtrace_symbols(array, size);
    if (strings != NULL) {
        for (size_t i = 0; i < size; i++) {
            size_t written = snprintf(info + offset, space_left, "%s\n", strings[i]);
            if (written >= space_left) {
                info[offset + space_left - 1] = '\0';
                break;
            }
            offset += written;
        }
        free(strings);
    } else {
        snprintf(info + offset, space_left, "canot get backtrace\n");
    }

    if (offset < info_size) {
        info[offset] = '\0';
    }
}
//TODO save backtrace info in ptr of callback
void get_backtrace_info1(char *info,int info_size){
    void *array[10];
    size_t size;
    char **strings;
	if(info==NULL || info_size==0){
	}
    size = backtrace(array, 10);

    strings = backtrace_symbols(array, size);

    printf("Stack trace:\n");
    for (size_t i = 0; i < size; i++) {
        printf("%s\n", strings[i]);
    }

    free(strings);
}

Node *find_node(void *real_ptr) {
    Node *current_node = head; 
    while (current_node != NULL) {
        if (current_node->real_ptr == real_ptr) {
            return current_node;
        }
        current_node = current_node->next;
    }
    return NULL;
}

void MWsignalHandler(int signum,siginfo_t *si, void *unused) {
	if (signum == SIGSEGV) {
		void *modify_ptr = si->si_addr;
		Node *node = find_node(modify_ptr);
		if(node == NULL){
			printf("cannot find node\n");
			return;
		}
		//printf("%p ================================         \n",node);
		//printf("Memory value at %p has been modified!\n", node->target_ptr);
		get_backtrace_info(node->backtrace_info_for_cb,node->backtrace_info_size_allowed);
		//restore to RW,or it will trigger SIGSEGV all the time util writing successfully
		int ret = mprotect(node->real_ptr, node->real_size, PROT_READ|PROT_WRITE);
		//printf("rw %p %p %p\n",node,node->target_ptr,node->real_ptr);
		if(ret != 0){
			printf("recover mem to rw,ret %d reason=%d\n",ret,errno);
		}
		//printf("node->real_ptr=%p, node->real_size=%d, rw ------------------------\n",node->real_ptr, node->real_size);
		free_node(node->real_ptr);
		if (node->cb != NULL) {
			node->cb(node->target_ptr, node->backtrace_info_for_cb);
		}
		//exit(1);
	}
}

void watcher_init(){
    struct sigaction sa;
    sa.sa_flags = SA_SIGINFO;
    sa.sa_sigaction = MWsignalHandler;
    sigemptyset(&sa.sa_mask);
    sigaction(SIGSEGV, &sa, NULL);
	inited = 1;
}

void watch(void **p, int size, Callback cb,char *info,int info_size) {
	Node *node = NULL;
	if(inited == 0) watcher_init();
	node = alloc_new_node(p,size,cb,info,info_size);
	
	int ret = mprotect(node->real_ptr, node->real_size, PROT_READ);
	/*Exec commamd "info proc mappings" in gdb
	 *Then we can see the memery is set to r like : 
          Start Addr           End Addr       Size     Offset  Perms  objfile
        0x7fb7ffe000       0x7fb7fff000     0x1000        0x0  r--p   
	 */
	if(ret != 0){
		printf("mprotect fail,ret %d reason=%d\n",ret,errno);
	}
	//printf("node->real_ptr=%p, node->real_size=%d, read only --------------------- \n",node->real_ptr, node->real_size);
}

